import java.awt.event.ActionListener;

/* Definition du type matrice a coefficients reels */

public class Matrice {
	
	/* Definition de l'exception d'erreur de dimension */
	class ErrDim extends IllegalArgumentException {
		ErrDim(String operation, Matrice m1, Matrice m2)
		{	System.out.println(operation+ " impossible entre une matrice "+m1.nbLignes()+"x"+m1.nbColonnes()+" et une matrice "+m2.nbLignes()+"x"+m2.nbColonnes()+" !"); }
		ErrDim(String operation, Matrice m)
		{	System.out.println(operation+ " impossible car la matrice n'est pas carree : elle est "+m.nbLignes()+"x"+m.nbColonnes()+" !"); }
	}

	/* Definition de l'exception d'erreur d'inversibilite */
	class ErrNonInversible extends IllegalArgumentException {
		ErrNonInversible()
		{	/*System.out.println("Determinant nul : matrice non inversible !");*/ }
	}
	
	
	/* Champs */
	protected double[][] coeff;

	/* Constructeur */
	public Matrice(double[][] tab)
	{	coeff=tab;}
	
	/** Renvoie le tableau des coefficients de la matrice courante 
	 *  @return le tableau des coefficients de la matrice courante 
	 */
	public double[][] getCoeff() 
	{	return coeff; }
	
	/** Renvoie le nombre de ligne de la matrice 
	 *  @return le nombre de ligne de la matrice
	 */
	public int nbLignes() 
	{	return coeff.length;}

	/** Renvoie le nombre de colonne de la matrice 
	 *  @return le nombre de colonne de la matrice
	 */
	public int nbColonnes() 
	{	return coeff[0].length;}

	/** Affiche sur la matrice sur le terminal */
	public void affiche() 
	{	for(int i=0; i<nbLignes(); i++)
		{	for(int j=0; j<nbColonnes(); j++)
			{	System.out.print(coeff[i][j]+" "); }
			System.out.println();
		}
	}

	/** Renvoie la somme de la matrice courante et m
	 *  @param m une matrice
	 *  @return le resultat de la somme de la matrice courante et m */
	public Matrice somme(Matrice m) {
		if(nbLignes()!=m.nbLignes()||nbColonnes()!=m.nbColonnes())
		{	throw new ErrDim("Addition",this,m);}
		double[][] somme= new double[nbLignes()][nbColonnes()];
		for(int i=0; i<nbLignes(); i++)
		{	for(int j=0; j<nbColonnes(); j++)
			{	somme[i][j]=coeff[i][j]+m.coeff[i][j]; }
		}
		return new Matrice(somme);
	}

	/** Renvoie le produit de la matrice courante par un scalaire
	 *  @param lambda un reel
	 *  @return le produit de la matrice courante par le reel lambda */
	public Matrice produit(double lambda)
	{	double[][] produit= new double[nbLignes()][nbColonnes()];
		for(int i=0; i<nbLignes(); i++)
		{	for(int j=0; j<nbColonnes(); j++)
			{	produit[i][j]=lambda*coeff[i][j]; }
		}
		return new Matrice(produit);
	}

	/** Renvoie le produit de la matrice courante par une matrice
	 *  @param m une matrice
	 *  @return le produit de la matrice courante par la matrice m */
	public Matrice produit(Matrice m)
	{	if(nbColonnes()!=m.nbLignes())
		{	throw new ErrDim("Produit",this,m);}
		double[][] produit= new double[nbLignes()][m.nbColonnes()];
		for(int i=0; i<nbLignes(); i++)
		{	for(int j=0; j<m.nbColonnes(); j++)
			{	for(int k=0; k<nbColonnes(); k++)
				{	produit[i][j]+=coeff[i][k]*m.coeff[k][j]; }
			}
		}
		return new Matrice(produit);
	}

	/** Renvoie la transposee de la matrice courante
	 *  @return la transposee de la matrice courante */
	public Matrice transposee()
	{	double[][] tab = new double[nbColonnes()][nbLignes()];
		for(int j=0; j<nbColonnes(); j++)
		{	for(int i=0; i<nbLignes(); i++)
			{	tab[i][j]=coeff[j][i]; }
		}
		return new Matrice(tab);
	}

	/** Renvoie la matrice courante privee de la ligne m et de la colonne n
	 *  @param m un entier
	 *  @param n un entier
	 *  @return la matrice courante privee de la ligne m et de la colonne n */
	public Matrice sousMatrice(int m, int n)	
	{	double[][] tab = new double[nbLignes()-1][nbColonnes()-1];
		int k=0;
		for(int i=0; i<nbLignes(); i++)
		{	for(int j=0; j<nbColonnes(); j++)
			{	if(i!=m&&j!=n)
				{	tab[k/tab.length][k%tab.length]=coeff[i][j];
					k++;
				}
			}
		}
		return new Matrice(tab);
	}
	
	/** Renvoie le determinant de la matrice courante
	 *  @return le determinant de la matrice courante */
	public double determinant()
	{	if(nbLignes()!=nbColonnes())
		{	throw new ErrDim("Calcul du determinant",this); }
		if(nbLignes()==1) //matrice 1x1
		{	return coeff[0][0]; }
		else
		{	double det=0.;
			for(int i=0; i<nbLignes(); i++)
			{	det+=coeff[i][0]*cofacteur(i,0);} //developpement par rapport a la colonne 0
			return det;
		}
	}

	/** Renvoie le cofacteur m,n de la matrice courante
	 *  @param m un entier
	 *  @param n un entier
	 *  @return le determinant de la matrice courante */
	public double cofacteur(int m, int n)
	{	return Math.pow(-1,m+n+1)*sousMatrice(m,n).determinant();}

	/** Renvoie la matrice des cofacteurs de la matrice courante
	 *  @return la matrice des cofacteurs de la matrice courante */
	public Matrice comatrice()
	{	if(nbLignes()!=nbColonnes())
		{	throw new ErrDim("Calcul de la comatrice",this); }
		double[][] tab = new double[nbLignes()][nbColonnes()];
		for(int i=0; i<nbLignes(); i++)
		{	for(int j=0; j<nbColonnes(); j++)
			{	tab[i][j]=cofacteur(i,j); }
		}
		return new Matrice(tab);
	}

	/** Renvoie l'inverse de la matrice courante
	 *  @return l'inverse de la matrice courante */
	public Matrice inverse() throws ErrNonInversible
	{	if(determinant()==0.)
		{	throw new ErrNonInversible(); }
		return comatrice().transposee().produit(1/determinant());
	}
	
	/** Cree une matrice de rotation selon un axe et un angle
	 * @param axe un vecteur
	 * @param angle un entier (degre)
	 * @return la matrice a appliquer pour effectuer une rotation d'angle angle selon l'axe axe */
	public static Matrice rotationM(Vecteur axe, double angle) {
		Vecteur u = axe.produit(1/axe.norme());
		double 	ux = u.get(1), 
				uy = u.get(2), 
				uz = u.get(3),
				c = Math.cos(Math.toRadians(angle)),
				s = Math.sin(Math.toRadians(angle));
		double[][] tab = new double[3][3];
		tab[0][0]= ux*ux+(1-ux*ux)*c; tab[0][1]= ux*uy*(1-c)-uz*s;  tab[0][2]= ux*uz*(1-c)+uy*s;
		tab[1][0]= ux*uy*(1-c)+uz*s;  tab[1][1]= uy*uy+(1-uy*uy)*c; tab[1][2]= uy*uz*(1-c)-ux*s;
		tab[2][0]= ux*uz*(1-c)-uy*s;  tab[2][1]= uy*uz*(1-c)+ux*s;  tab[2][2]= uz*uz+(1-uz*uz)*c;
		return new Matrice(tab);
	}
	
	/** Cree une matrice de passage de la base canonique vers la base (u,v,w)
	 * @param u un vecteur
	 * @param v un vecteur
	 * @param w un vecteur
	 * @return une matrice de passage de la base canonique vers la base (u,v,w) */
	public static Matrice passageM(Vecteur u, Vecteur v, Vecteur w) throws IllegalArgumentException  {
		double[][] tab = {	{	u.get(1),	v.get(1),	w.get(1)	},
							{	u.get(2),	v.get(2),	w.get(2)	},
							{	u.get(3),	v.get(3),	w.get(3)	}	};
		try {
			Matrice res = new Matrice(tab).inverse();
			return res;
		} catch(ErrNonInversible e) { 
			throw new IllegalArgumentException();
		}
	}
	
	/* Programme de test des differentes operations */
	public static void main(String[] args) {
	
		System.out.println("Matrice 1");
		double[][] tab1 = {{0,1,2},{1,2,3},{2,3,4}};
		Matrice m1 = new Matrice(tab1);
		m1.affiche();
		System.out.println();

		System.out.println("Matrice 2");
		double[][] tab2 = {{1,2,3},{3,3,3},{9,5,7}};
		Matrice m2 = new Matrice(tab2);
		m2.affiche();
		System.out.println();

		System.out.println("Somme m1+m2");
		m1.somme(m2).affiche();
		System.out.println();

		System.out.println("Produit 3*m1");
		m1.produit(3).affiche();
		System.out.println();

		System.out.println("Produit m1*m2");
		m1.produit(m2).affiche();
		System.out.println();

		System.out.println("determinant de m2 = "+m2.determinant());
		System.out.println();

		System.out.println("Transposee de m2");
		m2.transposee().affiche();
		System.out.println();

		System.out.println("inverse de m2");
		m2.inverse().affiche();
		
		

	}
}